#ifndef __BLOCK_QUEUE_HPP__
#define __BLOCK_QUEUE_HPP__

#include <pthread.h>
#include <queue>
#include <iostream>

template <typename T>
class BlockQueue
{
private:
    bool IsFull()
    {
        return _block_queue.size() == _cap;
    }
    bool IsEmpty()
    {
        return _block_queue.empty();
    }

public:
    BlockQueue(int cap) : _cap(cap)
    {
        _productor_wait_num = 0;
        _consumer_wait_num = 0;

        // 初始化锁和条件变量
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_product_cond, nullptr);
        pthread_cond_init(&_consum_cond, nullptr);
    }

    void Enqueue(T &in)
    {
        pthread_mutex_lock(&_mutex); // 加锁
        // if (IsFull())
        while (IsFull())
        {
            _productor_wait_num++;
            // 1、pthread_cond_wait调用是：a.让调用线程等待 b.自动释放曾经持有的锁 c.当条件满足，线程唤醒，pthread_cond_wait要求线程
            // 必须重新竞争锁，竞争成功方可返回
            pthread_cond_wait(&_product_cond, &_mutex);  // 伪唤醒
            _productor_wait_num--;
        }
        // 进行生产 写入数据
        _block_queue.push(in);
        // _block_queue.push(std::move(in));
        if(_consumer_wait_num > 0)
            pthread_cond_signal(&_consum_cond); // 唤醒 在解锁之前唤醒，则醒来就先去竞争锁，你一解锁，它就直接获得锁，在解锁之后唤醒，则醒来再竞争锁即可
        pthread_mutex_unlock(&_mutex);      // 解锁
        // pthread_cond_signal(&_consum_cond); // 唤醒 在解锁之后唤醒，其他线程还没有被唤醒，这个线程一醒来就竞争锁则直接获得你刚刚解开的锁
    }

    void Pop(T *out)
    {
        pthread_mutex_lock(&_mutex); // 加锁
        // if (IsEmpty())
        while (IsEmpty())
        {
            _consumer_wait_num++;
            pthread_cond_wait(&_consum_cond, &_mutex); // 假设这个函数调用失败了呢，线程就不会等待，直接向下执行，可能导致空指针访问
            _consumer_wait_num--;
        }
        // 进行消费
        *out = _block_queue.front();
        _block_queue.pop();
        if(_productor_wait_num>0)
            pthread_cond_signal(&_product_cond); // 唤醒
        pthread_mutex_unlock(&_mutex);       // 解锁
    }

    ~BlockQueue()
    {
        // 销毁锁和条件变量
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_product_cond);
        pthread_cond_destroy(&_consum_cond);
    }

private:
    std::queue<T> _block_queue;
    int _cap;
    pthread_mutex_t _mutex;
    pthread_cond_t _product_cond;
    pthread_cond_t _consum_cond;

    int _productor_wait_num;
    int _consumer_wait_num;
};

#endif



/*
有一个bug 因为假设只有1个生产者，5个消费者，生产者又生产的很慢，只生产了一个数据，
此时去把五个消费者全唤醒了，这5个消费者只有一个能竞争成功锁，它倒是美滋滋地使用了仅有的一个数据，
然后它释放锁，可是剩下的四个锁在这个竞争成功锁的消费者在使用数据的时候在干什么呢，他们也都已经被唤醒了啊，
我猜一直在竞争锁，只是锁被持有，他们一直竞争不到锁，当那个消费者释放锁时，剩下的四个也能竞争成功一个，
这个消费者持有锁了就也想使用数据，但是这个唯一的数据已经被前一个消费者使用了，因此直接向下执行 在*out = _block_queue.front(); 这里就会直接出错，访问空指针。
void Pop(T *out)
    {
        pthread_mutex_lock(&_mutex); // 加锁
        if (IsEmpty())                                      这里换成是while就可以解决这一问题 while(IsEmpty()) 
        {
            pthread_cond_wait(&_consum_cond, &_mutex);  第二个竞争成功锁的消费者不会再进行判断if即数据(阻塞队列)是否为空，因为他们是从这里的条件变量等待，在被生产者生产完了那里被唤醒的。
        }
        // 进行消费
        *out = _block_queue.front();
        _block_queue.pop();
        pthread_cond_signal(&_product_cond); // 唤醒
        pthread_mutex_unlock(&_mutex);       // 解锁
    }


    所以以后在写体哦阿健变量加互斥锁时，必须使用 while 来进行条件判断
*/